﻿using System.CodeDom;
using System.Collections.Generic;
using InterfaceWeaver.AopCore;
using Roslyn.Compilers.CSharp;

namespace InterfaceWeaver.CustomTool
{
    public class HookPropertySetAspectHandler : IAspectHandler
    {
        /// <summary>
        /// Handles the on namespace.
        /// </summary>
        /// <param name="dispatcher">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="namespaceDeclarationSyntax">The namespace declaration syntax.</param>
        /// <returns>
        /// True if the aspect is handled, false otherwise
        /// </returns>
        public bool HandleOnNamespace(AspectDispatcher dispatcher, AttributeSyntax attributeSyntax, NamespaceDeclarationSyntax namespaceDeclarationSyntax)
        {
            return attributeSyntax.IsOfType<HookSpecificPropertySetAttribute>()
                || attributeSyntax.IsOfType<HookGenericPropertySetAttribute>();
        }

        /// <summary>
        /// Determines whether the aspect is known and could be handled by aspect handler.
        /// </summary>
        /// <param name="dispatcher">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        public bool HandleOnInterfacePreview(AspectDispatcher dispatcher, AttributeSyntax attributeSyntax)
        {
            return attributeSyntax.IsOfType<HookSpecificPropertySetAttribute>()
                || attributeSyntax.IsOfType<HookGenericPropertySetAttribute>();
        }

        /// <summary>
        /// Regarding all the handler answering preview step, this step determines whether there are some aspect handler that should be removed from next live cycle.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="indirectAspectHandler"> </param>
        /// <param name="indirectAttributeSyntax"> </param>
        /// <returns></returns>
        public bool IsIndirectAspectsCompliantOnInterface(AspectDispatcher context, AttributeSyntax attributeSyntax, IAspectHandler indirectAspectHandler, AttributeSyntax indirectAttributeSyntax)
        {
            return !(indirectAspectHandler is MapDependencyPropertyAspectHandler);
        }

        /// <summary>
        /// Gets the handling priority on interface.
        /// </summary>
        /// <param name="dispatcher">The dispatcher.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="interfaceDeclarationSyntax">The interface declaration syntax.</param>
        /// <returns></returns>
        public AspectHandlingPriority GetHandlingPriorityOnInterface(AspectDispatcher dispatcher, AttributeSyntax attributeSyntax, InterfaceDeclarationSyntax interfaceDeclarationSyntax)
        {
            return AspectHandlingPriority.Normal;
        }

        /// <summary>
        /// Handles the on interface.
        /// </summary>
        /// <param name="dispatcher">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="interfaceDeclarationSyntax">The interface declaration syntax.</param>
        /// <returns>
        /// True if the aspect is handled, false otherwise
        /// </returns>
        public void HandleOnInterface(AspectDispatcher dispatcher, AttributeSyntax attributeSyntax, InterfaceDeclarationSyntax interfaceDeclarationSyntax)
        {
        }

        /// <summary>
        /// Determines whether the aspect is known and could be handled by aspect handler.
        /// </summary>
        /// <param name="context"> </param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <returns></returns>
        public bool HandleOnPropertyPreview(AspectDispatcher context, AttributeSyntax attributeSyntax)
        {
            return attributeSyntax.IsOfType<HookSpecificPropertySetAttribute>()
                || attributeSyntax.IsOfType<HookGenericPropertySetAttribute>();
        }

        /// <summary>
        /// Regarding all the handler answering preview step, this step determines whether there are some aspect handler that should be removed from next live cycle.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="indirectAspectHandler"> </param>
        /// <param name="indirectAttributeSyntax"> </param>
        /// <returns></returns>
        public bool IsIndirectAspectsCompliantOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, IAspectHandler indirectAspectHandler, AttributeSyntax indirectAttributeSyntax)
        {
            return !(indirectAspectHandler is MapDependencyPropertyAspectHandler);
        }

        /// <summary>
        /// Gets the handling priority on property.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="propertyDeclarationSyntax">The property declaration syntax.</param>
        /// <returns></returns>
        public AspectHandlingPriority GetHandlingPriorityOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, PropertyDeclarationSyntax propertyDeclarationSyntax)
        {
            return attributeSyntax.IsOfType<HookSpecificPropertySetAttribute>() 
                ? AspectHandlingPriority.BeforeNormal 
                : AspectHandlingPriority.Normal;
        }

        /// <summary>
        /// Handles the on member.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="attributeSyntax">The attribute syntax.</param>
        /// <param name="propertyDeclarationSyntax"> </param>
        /// <param name="handlerStreams"> </param>
        /// <returns>
        /// True if the aspect is handled, false otherwise
        /// </returns>
        public void HandleOnProperty(AspectDispatcher context, AttributeSyntax attributeSyntax, PropertyDeclarationSyntax propertyDeclarationSyntax, IDictionary<PropertyDeclarationSyntax, AspectTuple[]> handlerStreams)
        {
            if (context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterCodeCoreStream != null
                && context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterAssignStatment != null)
            {
                var setterStream = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterCodeCoreStream;
                var setValueExpression = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.SetterAssignStatment;
                var currentProperty = context.WeavedCodeContext.CurrentCodeMemberPropertyDeclaration.CurrentCodeMemberPropertyDeclaration;
                var currentType = context.WeavedCodeContext.CurrentTypeDeclaration;
                if (setterStream.Contains(setValueExpression))
                {
                    var index = setterStream.IndexOf(setValueExpression);

                    if (attributeSyntax.IsOfType<HookSpecificPropertySetAttribute>())
                    {
                        var valueChangingHookName = string.Format("OnProperty{0}ChangingCallBack", currentProperty.Name);
                        var valueChangedHookName = string.Format("OnProperty{0}ChangedCallBack", currentProperty.Name);
                        currentType.AddHookMethod(valueChangingHookName);
                        currentType.AddHookMethod(valueChangedHookName);

                        // Hook After assignement
                        {
                            var hook = new CodeExpressionStatement(
                                new CodeMethodInvokeExpression(
                                    new CodeThisReferenceExpression(),
                                    valueChangedHookName,
                                    new CodePrimitiveExpression(currentProperty.Name)));
                            if (index == setterStream.Count - 1)
                            {
                                setterStream.Add(hook);
                            }
                            else
                            {
                                setterStream.Insert(index + 1, hook);
                            }
                        }

                        // Hook before assignement
                        {
                            var hook = new CodeExpressionStatement(
                                new CodeMethodInvokeExpression(
                                    new CodeThisReferenceExpression(),
                                    valueChangingHookName,
                                    new CodePrimitiveExpression(currentProperty.Name)));
                            setterStream.Insert(index, hook);
                        }
                    }
                    if (attributeSyntax.IsOfType<HookGenericPropertySetAttribute>())
                    {
                        // Ensure the declaration of the partial method hooks
                        currentType.EnsureGenericPropertyChangesDeclaration();

                        // Hook After assignement
                        {
                            var hook = new CodeExpressionStatement(
                                new CodeMethodInvokeExpression(
                                    new CodeThisReferenceExpression(),
                                    "OnPropertyChangedCallBack",
                                    new CodePrimitiveExpression(currentProperty.Name)));
                            if (index == setterStream.Count - 1)
                            {
                                setterStream.Add(hook);
                            }
                            else
                            {
                                setterStream.Insert(index + 1, hook);
                            }
                        }

                        // Hook before assignement
                        {
                            var hook = new CodeExpressionStatement(
                                new CodeMethodInvokeExpression(
                                    new CodeThisReferenceExpression(),
                                    "OnPropertyChangingCallBack",
                                    new CodePrimitiveExpression(currentProperty.Name)));
                            setterStream.Insert(index, hook);
                        }
                    }
                }
            }
        }
    }
}
